package com.code.util;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import xjavadoc.SourceClass;
import xjavadoc.XClass;
import xjavadoc.XJavaDoc;
import xjavadoc.XMethod;
import xjavadoc.XTagFactory;
import xjavadoc.filesystem.XJavadocFile;

import com.code.template.AbstractTypeData;
import com.code.template.TypeFileTemplate;

public class CodeUtil {
	public static String newTab(int count) {
		StringBuilder b = new StringBuilder();
		while (count-- > 0)
			b.append("\t");
		return b.toString();
	}

	@SuppressWarnings("rawtypes")
	public static void generateSVCode(List<Map<String, ?>> list,
			String outputDir, ICodeHelper helper, List<String> err)
			throws IOException {
		LogUtil.info("genrate code start!");
		HashMap<String, List<Map>> interface2Svmap = new HashMap<String, List<Map>>();
		HashMap<String, List<Map>> impl2Svmap = new HashMap<String, List<Map>>();
		HashMap<String, SourceClass> typeName2XClass = new HashMap<String, SourceClass>();
		for (Map<String, ?> map1 : list) {
			String srvInterface = (String) map1.get(ICodeHelper.SRV_INTERFACE);
			List<Map> list2 = interface2Svmap.get(srvInterface);
			if (list2 == null) {
				list2 = new ArrayList<Map>();
				interface2Svmap.put(srvInterface, list2);
			}
			list2.add(map1);

			String srvImpl = (String) map1.get(ICodeHelper.SRV_IMPL_CLASS);
			if (StrUtil.isEmpty(srvImpl))
				srvImpl = new StringBuilder(srvInterface.replace(
						".interfaces.I", ".impl.")).append("Impl").toString();
			list2 = impl2Svmap.get(srvImpl);
			if (list2 == null) {
				list2 = new ArrayList<Map>();
				impl2Svmap.put(srvImpl, list2);
			}
			list2.add(map1);
		}
		if (ICodeHelper.JAVA.equals(helper.getCodeLang())) {
			for (Entry<String, List<Map>> svmapEntry : interface2Svmap
					.entrySet()) {
				doGenerateSVCode(outputDir, helper, err, typeName2XClass,
						svmapEntry.getKey(), true, svmapEntry.getValue());
			}
			for (Entry<String, List<Map>> svmapEntry : impl2Svmap.entrySet()) {
				doGenerateSVCode(outputDir, helper, err, typeName2XClass,
						svmapEntry.getKey(), false, svmapEntry.getValue());
			}
		} else if (ICodeHelper.C.equals(helper.getCodeLang())) {
			// generate c code here
		}
		LogUtil.info("genrate code finished!");
	}

	@SuppressWarnings("rawtypes")
	private static void doGenerateSVCode(String outputDir, ICodeHelper helper,
			List<String> err, HashMap<String, SourceClass> typeName2XClass,
			final String typeName, boolean isInterface, List<Map> services)
			throws IOException {
		final int lastIndex = typeName.lastIndexOf(".");
		SourceClass xclass = typeName2XClass.get(typeName);
		File sourceFile = new File(new StringBuilder(outputDir).append("/")
				.append(typeName.replace('.', '/'))
				.append(ICodeHelper.DOT_JAVA).toString());
		final Map<String, String> importTypeMap = new HashMap<String, String>();
		final List<String> methodsDeclare = new ArrayList<String>();
		String srvInterface = "";
		for (Map map : services) {
			String serviceCode = (String) map.get(ICodeHelper.SERVICE_CODE);
			String _version = (String) map.get(ICodeHelper.VERSION);
			if (StrUtil.isEmpty(srvInterface))
				srvInterface = (String) map.get(ICodeHelper.SRV_INTERFACE);
			try {
				String serviceName = (String) map.get(ICodeHelper.SRV_METHOD);
				StringBuilder paraStr = new StringBuilder();
				String retType = (String) map.get(ICodeHelper.SRV_RETURN);
				Collection<Map<String, ?>> params = helper.getSvRootParams(
						serviceCode, _version);
				if (params != null) {
					int i = 0;
					for (Map<String, ?> paramBean : params) {
						if (ICodeHelper.IN.equals(paramBean
								.get(ICodeHelper.PARAM_INOUT))) {
							if (i == 0)
								i++;
							else
								paraStr.append(", ");
							paraStr.append(
									getShortNameByImport((String) paramBean
											.get(ICodeHelper.PARAM_TYPE),
											importTypeMap))
									.append(" ")
									.append(paramBean
											.get(ICodeHelper.PARAM_KEY));
						} else {
							retType = (String) paramBean
									.get(ICodeHelper.PARAM_TYPE);
						}
					}
				}
				if (StrUtil.isEmpty(retType))
					retType = ICodeHelper.VOID;
				else
					retType = resolveType(retType);
				retType = getShortNameByImport(retType, importTypeMap);

				if (sourceFile.exists()) {
					if (xclass == null) {
						XJavaDoc xJavaDoc = new XJavaDoc();
						XTagFactory tagFactory = new XTagFactory();
						xclass = new SourceClass(xJavaDoc, new XJavadocFile(
								sourceFile), true, tagFactory,
								ICodeHelper.UTF_8);
						isInterface = xclass.isInterface();
						typeName2XClass.put(typeName, xclass);
					}
					if (isMethodExsit(xclass, serviceName))
						continue;
				}
				StringBuilder methodB = new StringBuilder("public ")
						.append(retType).append(" ").append(serviceName)
						.append("(").append(paraStr).append(")");
				if (isInterface) {
					methodB.delete(0, "public ".length()).append(";");
					// add method annotation declares
					if (helper.getCodeAnnotation())
						methodB.insert(0,
								helper.getAnnotationService(map, importTypeMap));
				} else {
					StringBuilder retExpr = new StringBuilder(0);
					if (!retType.equals(ICodeHelper.VOID)) {
						String defaultVal = "null";
						String javaType = JavaTypeConstant.javaTypes
								.get(retType);
						if (javaType != null && !javaType.contains(".")) {
							defaultVal = "boolean".equals(javaType) ? Boolean.FALSE
									.toString() : "0";
						}
						retExpr.append("\treturn ").append(defaultVal)
								.append(";\n");
					}
					methodB.append("{\n").append(retExpr).append("}");
				}
				methodsDeclare.add(methodB.toString());
				LogUtil.info("info-serviceCode:" + serviceCode);
			} catch (Exception e) {
				String msg = "err-serviceCode:" + serviceCode + "\nCaused by "
						+ e.toString();
				LogUtil.err(msg);
				err.add(msg);
			}
		}
		String code = "";
		if (!sourceFile.exists()) {
			createFile(sourceFile.getAbsolutePath());
			final String type = isInterface ? "interface " : "class ";
			final String superType = isInterface ? "" : new StringBuffer(
					"implements ")
					.append(getShortNameByImport(srvInterface, importTypeMap))
					.append(" ").toString();
			code = new TypeFileTemplate(new AbstractTypeData() {
				public String getPackage() {
					if (lastIndex == -1)
						return "";
					return new StringBuilder("package ")
							.append(typeName.substring(0, lastIndex))
							.append(";").toString();
				};

				@Override
				public Collection<String> getImportTypes() {
					return importTypeMap.values();
				}

				public String getTypeName() {
					return lastIndex == -1 ? typeName
							: typeName.substring(lastIndex + 1) + " ";
				};

				public String getType() {
					return type;
				};

				@Override
				public String getSuperTypeName() {
					return superType;
				}

				public java.util.List<String> getMethodsDeclare() {
					return methodsDeclare;
				};
			}).toCode();

		} else {
			StringBuilder codeB = new StringBuilder(FileUtil.readFileToString(
					sourceFile, ICodeHelper.UTF_8));
			if (!importTypeMap.isEmpty()) {
				int headIndex = codeB.indexOf("public ");
				String head = "";
				if (headIndex != -1)
					head = codeB.substring(0, headIndex);
				List<String> importList = getImportDeclareList(importTypeMap
						.values());
				Iterator<String> iterator = importList.iterator();
				while (iterator.hasNext())
					if (head.contains(iterator.next()))
						iterator.remove();
				if (importList.size() > 0) {
					StringBuilder importB = new StringBuilder("\n")
							.append(getImportDeclaresByImportList(importList));
					int pIndex = codeB.indexOf("package ");
					int importIndex = 0;
					if (pIndex == 0
							|| (pIndex > 0 && ('\n' == codeB.charAt(pIndex - 1)))) {
						importIndex = codeB.indexOf("\n", pIndex) + 1;
					}
					codeB.insert(importIndex, importB);
				}
			}
			StringBuilder methodB = new StringBuilder();
			for (String method : methodsDeclare) {
				String newTab = CodeUtil.newTab(1);
				methodB.append(newTab)
						.append(StrUtil.replace(method, "\n", "\n" + newTab))
						.append("\n");
			}
			code = codeB.insert(codeB.lastIndexOf("}"), methodB).toString();
		}
		FileUtil.writeStringToFile(sourceFile, code, ICodeHelper.UTF_8);
		LogUtil.info("info-sourceFile:" + sourceFile);
	}

	private static void createFile(String absolutePath) throws IOException {
		Path path = Paths.get(absolutePath);
		Path parent = path.getParent();
		if (!Files.exists(parent))
			Files.createDirectories(parent);
		if (Files.exists(path))
			throw new IOException(path + "already exsit!");
		Files.createFile(path);
	}

	public static String getShortNameByImport(String name,
			Map<String, String> importTypeMap) {
		int lIndex = name.lastIndexOf(".");
		if (lIndex != -1) {
			String shortName = name.substring(lIndex + 1);
			String typeName = importTypeMap.get(shortName);
			if (typeName == null) {
				importTypeMap.put(shortName, getPureType(name));
				name = shortName;
			} else if (StrUtil.equals(typeName, name))
				name = shortName;
		}
		return name;
	}

	private static boolean isMethodExsit(XClass xclass, String serviceName) {
		@SuppressWarnings("unchecked")
		List<XMethod> methods = xclass.getMethods();
		for (XMethod xMethod : methods) {
			if (serviceName.equals(xMethod.getName()))
				return true;
		}
		return false;
	}

	public static void generateParamTypeCode(List<Map<String, ?>> list,
			Collection<String> skippedTypes, String outputDir,
			ICodeHelper helper, List<String> err) {
		LogUtil.info("genrate bean code start!");
		Collection<Map<String, Collection<Map<String, ?>>>> parentKey2ParamMaps = new ArrayList<Map<String, Collection<Map<String, ?>>>>();
		for (Map<String, ?> mapBean : list) {
			parentKey2ParamMaps.add(helper.getSvParamMapParentKey(
					(String) mapBean.get(ICodeHelper.SERVICE_CODE),
					(String) mapBean.get(ICodeHelper.VERSION)));
		}

		if (ICodeHelper.JAVA.equals(helper.getCodeLang())) {
			for (Map<String, Collection<Map<String, ?>>> map : parentKey2ParamMaps) {
				Collection<Map<String, ?>> rootParams = map.get(null);
				if (rootParams == null)
					continue;
				for (Map<String, ?> param : rootParams) {
					try {
						doGenerateParamTypeCode(param, map, skippedTypes,
								outputDir);
					} catch (IOException e) {
						String msg = "err-type:"
								+ param.get(ICodeHelper.PARAM_TYPE)
								+ "\nCaused by " + e.toString();
						LogUtil.err(msg);
						err.add(msg);
					}
				}
			}
		} else if (ICodeHelper.C.equals(helper.getCodeLang())) {
			// generate c code here
		}
		LogUtil.info("genrate bean code finished!");
	}

	public static void doGenerateParamTypeCode(Map<String, ?> param,
			Map<String, Collection<Map<String, ?>>> map,
			Collection<String> skippedTypes, String outputDir)
			throws IOException {
		final String typeName = getPureType(resolveType((String) param
				.get(ICodeHelper.PARAM_TYPE)));
		if (skippedTypes.contains(typeName) || isTypeExsit(typeName))
			return;
		skippedTypes.add(typeName);
		String parentParamKey = param.get(ICodeHelper.PARAM_ID) + "";
		final int lastIndex = typeName.lastIndexOf(".");
		final boolean isInterface = typeName.contains(".interfaces.I");
		File sourceFile = new File(new StringBuilder(outputDir).append("/")
				.append(typeName.replace('.', '/'))
				.append(ICodeHelper.DOT_JAVA).toString());
		final Map<String, String> importTypeMap = new HashMap<String, String>();
		final List<String> attributesDeclare = new ArrayList<String>();
		final List<String> methodsDeclare = new ArrayList<String>();
		Collection<Map<String, ?>> set = map.get(parentParamKey);
		if (set != null)
			for (Map<String, ?> attr : set) {
				doGenerateParamTypeCode(attr, map, skippedTypes, outputDir);
				StringBuilder attrB = new StringBuilder();
				StringBuilder getMethodB = new StringBuilder();
				StringBuilder setMethodB = new StringBuilder();
				String attrType = (String) attr.get(ICodeHelper.PARAM_TYPE);
				attrType = resolveType(attrType);
				attrType = getShortNameByImport(attrType, importTypeMap);
				String attrName = (String) attr.get(ICodeHelper.PARAM_KEY);
				attrB.append("private ").append(attrType).append(" ")
						.append(attrName).append(";");
				if (isInterface) {
					attrB.delete(0, "private ".length());
				} else {
					String attrNameFUp = attrName;
					{
						char[] charArray = attrName.toCharArray();
						charArray[0] = Character.toUpperCase(charArray[0]);
						attrNameFUp = new String(charArray);
					}
					getMethodB.append("public ").append(attrType);
					if (attrType.toLowerCase().equals("boolean")
							|| attrType.equals(Boolean.class.getName()))
						getMethodB.append(" is");
					else
						getMethodB.append(" get");
					getMethodB.append(attrNameFUp)
							.append("(){\n\treturn this.").append(attrName)
							.append(";\n}");
					setMethodB.append("public  void").append(" set")
							.append(attrNameFUp).append("(").append(attrType)
							.append(" ").append(attrName).append("){\n\tthis.")
							.append(attrName).append("=").append(attrName)
							.append(";\n}");
				}
				attributesDeclare.add(attrB.toString());
				methodsDeclare.add(getMethodB.toString());
				methodsDeclare.add(setMethodB.toString());
			}
		createFile(sourceFile.getAbsolutePath());
		FileUtil.writeStringToFile(sourceFile, new TypeFileTemplate(
				new AbstractTypeData() {
					@Override
					public String getType() {
						return isInterface ? "interface " : super.getType();
					}

					@Override
					public String getPackage() {
						if (lastIndex == -1)
							return "";
						return new StringBuilder("package ")
								.append(typeName.substring(0, lastIndex))
								.append(";").toString();
					}

					@Override
					public Collection<String> getImportTypes() {
						return importTypeMap.values();
					}

					@Override
					public String getTypeName() {
						return lastIndex == -1 ? typeName : typeName
								.substring(lastIndex + 1) + " ";
					}

					@Override
					public List<String> getAttributesDeclare() {
						return attributesDeclare;
					}

					@Override
					public List<String> getMethodsDeclare() {
						return methodsDeclare;
					}

				}).toCode(), ICodeHelper.UTF_8);
		LogUtil.info("info-sourceFile:" + sourceFile);

	}

	private static String resolveType(String type) {
		int index = -1;
		while (type.charAt(++index) == '[')
			;
		if (index == 0)
			return type;
		type = type.substring(index);
		while (index-- > 0)
			type += "[]";
		return type;
	}

	private static boolean isTypeExsit(String paramType) {
		if (JavaTypeConstant.mappingTypes.containsKey(paramType))
			return true;
		try {
			Class.forName(paramType);
			return true;
		} catch (ClassNotFoundException e) {
			return false;
		}
	}

	private static String getPureType(String type) {
		int index = StrUtil.indexOfAny(type, "<[ ");
		if (index != -1)
			type = type.substring(0, index);
		return type;
	}

	public static List<String> getImportDeclareList(
			Collection<String> importTypes) {
		if (importTypes == null || importTypes.isEmpty())
			return Collections.emptyList();
		ArrayList<String> list = new ArrayList<String>();
		for (String importType : importTypes) {
			list.add(String.format("import %s;", importType));
		}
		return list;
	}

	public static String getImportDeclaresByTypes(Collection<String> importTypes) {
		return getImportDeclaresByImportList(getImportDeclareList(importTypes));
	}

	public static String getImportDeclaresByImportList(
			Collection<String> imports) {
		if (imports == null || imports.isEmpty())
			return "";
		return new StringBuilder(StrUtil.join(imports.iterator(), "\n"))
				.append("\n").toString();
	}
}
